#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[2] = {0, 0};

/**
  * @brief  Initializes the DMA to thransfer data from ADC1->DDR to AD_Value (AD_Value is the SRAM address)
  * @param  AddrA: target address
  * @retval None
  */
void AD_Init(void)
{
	// 第一步: 开启RCC时钟: ADC1 ，PA，DMA 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	
	// 第二步: 配置 ADDCLK 的分频器。  72M / 6 = 12
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	// 第三步: 配置对应的 GPIO，把需要用的GPIO配置成模拟输入的模式。
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;           // 模拟输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_3;  // 选择引脚: PA0~PA7
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       // 输出速度没有特殊要求选择 50MHz 即可
	GPIO_Init(GPIOA, &GPIO_InitStructure);                  // 初始化 A 分组下的0引脚	
	
	// 第四步: 配置多路开关，将左边的通道接入到右边的规则组列表里面。（将通道写入到列表里面）
	// 参数1: 具体ADC外设
	// 参数2: 通道编号
	// 参数3: 列表当中位序, 编号从 1~16
	// 参数4: 采样时间, 看笔记当中的描述, 配置越短越快, 配置的越长越稳定
	// 在规则组菜单列表的第一个位置写入通道0这个通道
	// 左边接通道 1 (电位器) 和 通道 3 (红外传感器)
	ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 2, ADC_SampleTime_55Cycles5);
	
	// 第四步: 1. ACD 结构体初始化 配置 ADC 转换器：ADC 单次转换还是连续转换，扫描还是非扫描，有几个通道，触发源是什么，是左对齐还是右对齐。
	ADC_InitTypeDef ADC_InitStructure;
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                          // 配置为独立工作模式还是双 ADC 模式
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                      // 数据对齐
#if ADC_CONTINUE_SCAN_AND_DMA_LOOP
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                          // 连续转换
#else
	ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                         // 单个转换
#endif
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;         // 外部触发源, 不需要, 我们软件触发 ADC 即可
	ADC_InitStructure.ADC_NbrOfChannel = 2;                                     // 在指定扫描模式下, 总共会用到几个通道
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;                                // 扫描模式开启
	ADC_Init(ADC1, &ADC_InitStructure);
	
	// 第四步: 2. DMA结构体初始化
	DMA_InitTypeDef DMA_InitStructure;
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(ADC1->DR));          // 外设地址: ADC1 的 DR 地址
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  // 数据宽度: 要的是DR 寄存器的低 16 位的数据, 因此配置成半字
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;             // 外设地址是否自增, DR寄存器就一个, 不用自增
	
	DMA_InitStructure.DMA_MemoryBaseAddr = ((uint32_t)AD_Value);                 // 存储器地址: SRAM 的 AD_Value 数组中
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          // 数据宽度: 半字
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                      // 存储器地址是否自增, 数组两个元素, 需要自增
	
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;                           // 外设地址作为源: 即外设站点 -> 存储器站点
	DMA_InitStructure.DMA_BufferSize = 2;                                        // 源端站点的 DataSize, BufferSize 就是传输寄存器的值, 2, 两个ADC通道
#if ADC_CONTINUE_SCAN_AND_DMA_LOOP
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;                              // 传输计数器是否自动重装: DMA_Mode_Circular 是循环模式
#else
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                                // 传输计数器是否自动重装: DMA_Mode_Normal 正常模式, 不重装
#endif
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                                 // 外设 到 存储器的模式: 硬件触发
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;                        // 指定通道的软件优先级, 如果有多个通道的话, 配置紧急通道优先转运
	DMA_Init(DMA1_Channel1, &DMA_InitStructure);                                 // DMA1 请求映像中 ADC1 必须在通道1
	
	// 第五步: DMA使能
	DMA_Cmd(DMA1_Channel1, DISABLE);
	
	// 第六步: ADC 使能之前开启 ADC 到 DMA 的输出
	ADC_DMACmd(ADC1, ENABLE);
	
	// 第七步: 开关控制：ADC_Cmd 开启ADC。
	ADC_Cmd(ADC1, ENABLE);
	
	// 第八步: 对 ADC 进行一下校准，这样可以减小误差。
	ADC_ResetCalibration(ADC1);                                                 // 复位校准, 软件设置对应标志位, 复位校准完成会被硬件清除
	while(ADC_GetResetCalibrationStatus(ADC1) == SET)                           // 获取复位校准状态
	;                                                                           
	ADC_StartCalibration(ADC1);                                                 // 开始校准
	while(ADC_GetCalibrationStatus(ADC1) == SET)                                // 获取开始校准状态
	;
	
#if ADC_CONTINUE_SCAN_AND_DMA_LOOP
	
	// 第九步: 如果是 ADC 连续转换, 扫描 + DMA 循环计数, 则直接在初始化的时候触发一次 ADC 启动即可
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);
	
#endif
}

/**
  * @brief  An ADC scan is triggered to move the data of the two channels into the ADValue array
  * @retval None
  */
void AD_GetValue(void)
{	
#if !ADC_CONTINUE_SCAN_AND_DMA_LOOP
	// 1. DMA 也是非循环模式, 每次触发ADC的值之前都需要写入一下传输寄存器的值
	DMA_Cmd(DMA1_Channel1, DISABLE);
	DMA_SetCurrDataCounter(DMA1_Channel1, 2);
	DMA_Cmd(DMA1_Channel1, ENABLE);
	
	// 2. 软件触发转换: 因为ADC是单次模式
	ADC_SoftwareStartConvCmd(ADC1, ENABLE);

	// 3. 等待 DMA 完成, ADC 先于 DMA 完成
	// 等待数据转运完成
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET)
	;
	// 手动清除标志位
	DMA_ClearFlag(DMA1_FLAG_TC1);
#endif
}